/* 检测数据类型 */
const class2type = {},
  toString = class2type.toString,
  hasOwn = class2type.hasOwnProperty;
const toType = function toType(obj) {
  let reg = /^\[object ([\w\W]+)\]$/;
  if (obj == null) return obj + "";
  return typeof obj === "object" || typeof obj === "function"
    ? reg.exec(toString.call(obj))[1].toLowerCase()
    : typeof obj;
};
const isFunction = function isFunction(obj) {
  return (
    typeof obj === "function" &&
    typeof obj.nodeType !== "number" &&
    typeof obj.item !== "function"
  );
};
const isWindow = function isWindow(obj) {
  return obj != null && obj === obj.window;
};
const isArrayLike = function isArrayLike(obj) {
  let length = !!obj && "length" in obj && obj.length,
    type = toType(obj);
  if (isFunction(obj) || isWindow(obj)) return false;
  return (
    type === "array" ||
    length === 0 ||
    (typeof length === "number" && length > 0 && length - 1 in obj)
  );
};
const isPlainObject = function isPlainObject(obj) {
  let proto, Ctor;
  if (!obj || toString.call(obj) !== "[object Object]") return false;
  proto = Object.getPrototypeOf(obj);
  if (!proto) return true;
  Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
  return typeof Ctor === "function" && Ctor === Object;
};
const isEmptyObject = function isEmptyObject(obj) {
  let keys = Object.getOwnPropertyNames(obj);
  if (typeof Symbol !== "undefined")
    keys = keys.concat(Object.getOwnPropertySymbols(obj));
  return keys.length === 0;
};
const isNumeric = function isNumeric(obj) {
  var type = toType(obj);
  return (
    (type === "number" || type === "string") && !isNaN(obj - parseFloat(obj))
  );
};

/* 函数的防抖和节流 */
const clearTimer = function clearTimer(timer) {
  if (timer) clearTimeout(timer);
  return null;
};
const debounce = function debounce(func, wait, immediate) {
  if (typeof func !== "function")
    throw new TypeError("func is not a function!");
  if (typeof wait === "boolean") {
    immediate = wait;
    wait = undefined;
  }
  wait = +wait;
  if (isNaN(wait)) wait = 300;
  if (typeof immediate !== "boolean") immediate = false;
  let timer = null;
  return function operate(...params) {
    let now = !timer && immediate;
    timer = clearTimer(timer);
    timer = setTimeout(() => {
      if (!immediate) func.call(this, ...params);
      timer = clearTimer(timer);
    }, wait);
    if (now) func.call(this, ...params);
  };
};
const throttle = function throttle(func, wait) {
  if (typeof func !== "function")
    throw new TypeError("func is not a function!");
  wait = +wait;
  if (isNaN(wait)) wait = 300;
  let timer = null,
    previous = 0;
  return function operate(...params) {
    let now = +new Date(),
      remaining = wait - (now - previous);
    if (remaining <= 0) {
      func.call(this, ...params);
      previous = +new Date();
      timer = clearTimer(timer);
    } else if (!timer) {
      timer = setTimeout(() => {
        func.call(this, ...params);
        previous = +new Date();
        timer = clearTimer(timer);
      }, remaining);
    }
  };
};

/* 数组和对象的操作 */
const mergeArray = function mergeArray(first, second) {
  if (typeof first === "string") first = Object(first);
  if (typeof second === "string") second = Object(second);
  if (!isArrayLike(first)) first = [];
  if (!isArrayLike(second)) second = [];
  let len = +second.length,
    j = 0,
    i = first.length;
  for (; j < len; j++) {
    first[i++] = second[j];
  }
  first.length = i;
  return first;
};
const each = function each(obj, callback) {
  let isArray = isArrayLike(obj),
    isObject = isPlainObject(obj);
  if (!isArray && !isObject)
    throw new TypeError("obj must be a array or likeArray or plainObject");
  if (!isFunction(callback)) throw new TypeError("callback is not a function");
  if (isArray) {
    for (let i = 0; i < obj.length; i++) {
      let item = obj[i],
        index = i;
      if (callback.call(item, item, index) === false) break;
    }
    return obj;
  }
  let keys = Object.getOwnPropertyNames(obj);
  if (typeof Symbol !== "undefined")
    keys = keys.concat(Object.getOwnPropertySymbols(obj));
  for (let i = 0; i < keys.length; i++) {
    let key = keys[i],
      value = obj[key];
    if (callback.call(value, value, key) === false) break;
  }
  return obj;
};
const merge = function merge(...params) {
  let options,
    target = params[0],
    i = 1,
    length = params.length,
    deep = false,
    treated = params[length - 1];
  toType(treated) === "set" ? length-- : (treated = new Set());
  if (typeof target === "boolean") {
    deep = target;
    target = params[i];
    i++;
  }
  if (target == null || (typeof target !== "object" && !isFunction(target)))
    target = {};
  for (; i < length; i++) {
    options = params[i];
    if (options == null) continue;
    if (treated.has(options)) return options;
    treated.add(options);
    each(options, (copy, name) => {
      let copyIsArray = Array.isArray(copy),
        copyIsObject = isPlainObject(copy),
        src = target[name];
      if (deep && copy && (copyIsArray || copyIsObject)) {
        if (copyIsArray && !Array.isArray(src)) src = [];
        if (copyIsObject && !isPlainObject(src)) src = {};
        target[name] = merge(deep, src, copy, treated);
      } else if (copy !== undefined) {
        target[name] = copy;
      }
    });
  }
  return target;
};
const clone = function clone(...params) {
  let target = params[0],
    deep = false,
    length = params.length,
    i = 1,
    isArray,
    isObject,
    result,
    treated;
  if (typeof target === "boolean" && length > 1) {
    deep = target;
    target = params[1];
    i = 2;
  }
  treated = params[i];
  if (!treated) treated = new Set();
  if (treated.has(target)) return target;
  treated.add(target);
  isArray = Array.isArray(target);
  isObject = isPlainObject(target);
  if (target == null) return target;
  if (
    !isArray &&
    !isObject &&
    !isFunction(target) &&
    typeof target === "object"
  ) {
    try {
      return new target.constructor(target);
    } catch (_) {
      return target;
    }
  }
  if (!isArray && !isObject) return target;
  result = new target.constructor();
  each(target, (copy, name) => {
    if (deep) {
      result[name] = clone(deep, copy, treated);
      return;
    }
    result[name] = copy;
  });
  return result;
};

/* 设定具备有效期的localStorage存储方案 */
const storage = {
  set(key, value) {
    localStorage.setItem(
      key,
      JSON.stringify({
        time: +new Date(),
        value,
      })
    );
  },
  get(key, cycle = 2592000000) {
    cycle = +cycle;
    if (isNaN(cycle)) cycle = 2592000000;
    let data = localStorage.getItem(key);
    if (!data) return null;
    let { time, value } = JSON.parse(data);
    if (+new Date() - time > cycle) {
      storage.remove(key);
      return null;
    }
    return value;
  },
  remove(key) {
    localStorage.removeItem(key);
  },
};

/* 日期格式化 */
const formatTime = function formatTime(time, template) {
  if (typeof time !== "string") {
    time = new Date().toLocaleString("zh-CN", { hour12: false });
  }
  if (typeof template !== "string") {
    template = "{0}年{1}月{2}日 {3}:{4}:{5}";
  }
  let arr = [];
  if (/^\d{8}$/.test(time)) {
    let [, $1, $2, $3] = /^(\d{4})(\d{2})(\d{2})$/.exec(time);
    arr.push($1, $2, $3);
  } else {
    arr = time.match(/\d+/g);
  }
  return template.replace(/\{(\d+)\}/g, (_, $1) => {
    let item = arr[$1] || "00";
    if (item.length < 2) item = "0" + item;
    return item;
  });
};

const utils = {
  toType,
  isFunction,
  isWindow,
  isArrayLike,
  isPlainObject,
  isEmptyObject,
  isNumeric,
  debounce,
  throttle,
  mergeArray,
  each,
  merge,
  clone,
  storage,
  formatTime,
};

/* 处理冲突 */
if (typeof window !== "undefined") {
  let $ = window._;
  utils.noConflict = function noConflict() {
    if (window._ === utils) {
      window._ = $;
    }
    return utils;
  };
}

/* 导出API */
export default utils;
